#include <stdio.h>

#include "../6502.h"
#include "../6502_CPUMacros.h"
#include "DBT.h"
#include "Profiler.h"
#include "CFG.h"
#include "TU.h"
#include "Tarjan.h"
#include "Cycles.h"
#include "../Statistics.h"

extern Statistics* ExecutionStatistics;

extern ProfData* ProfilerData;

extern __Context CPU;

int32_t DBT_Init(void){
    BBRInit();
    CFGRInit();
    ProfilerInit();  
    TARJAN_Init();
}

int32_t DBT_Main(int32_t Cycles){
    
    static TranslationUnit* PreviousTU = NULL;
    uint32_t Interrupt = 0;
    uint64_t Temperature;
    
    Cycles = HandleInterrupts(Cycles, &Interrupt);
    
    //No currently executing CFG
    if(!ProfilerData->CurrentCFG){
        //Creates a new CFG and inputs it into the profiler data structure
        //Effectively sets ProfilerData->CurrentCFG and other data structures
        SetNewCFG();
    }      
    
    //if(Interrupt){
    //    CFGRRefresh(ProfilerData->CurrentCFG); 
    //    SetNewCFG();
    //}
    
    while(Cycles > 0){
    
        //TODO: Check whether or not there is translated code for current PC
        //If so, call it.

        PreviousTU = ProfilerData->CurrentTU;      
        
        //No fused CFGs aren't compiled. Therefore, all CFG nodes in the CFG graph
        //are compiled. Thus, we can assure that, if the current PC is associated with
        //any CFG nodes (not BB nodes), there's translated code available for it.
        BasicBlock *InBB;
        CFGNode* CurrentNode;
        InBB = BBRSearch(CPU.PC);
        if(InBB){
            CurrentNode = CFG_SearchNode(ProfilerData->CurrentCFG, InBB->StartingAddress);
            if(TU_GetCFG(CurrentNode->Unit)){
                ProfilerData->Interpreting = 0;
                
                if(InBB->StartingAddress == CPU.PC){
                    CurrentNode->EpochExecutionCount++;
                    if(ProfilerData->CurrentTU != CurrentNode->Unit)
                        ExecutionStatistics->CurrentEpochInfo->CompiledCalls++;
                }
                
                ProfilerData->CurrentTU = CurrentNode->Unit;
                
                if(TU_GetCFG(CurrentNode->Unit)->InitialAddress == CPU.PC)
                    ExecutionStatistics->DBT_TotalExecutionCompiledCalls++;
                
            }
            else
                ProfilerData->Interpreting = 1;
          
        }
        else
            ProfilerData->Interpreting = 1;
        
        if(!ProfilerData->Interpreting){
            ExecutionStatistics->DBT_CompiledInstructionsExecuted++;
            ExecutionStatistics->CurrentEpochInfo->CompiledInstructions++;
        }
        
        ExecutionStatistics->TotalInstructionsExecuted++;
        ExecutionStatistics->CurrentEpochInfo->TotalInstructions++;
        
        //Else, interpret next instruction
        //Instruction profiling is done for every interpreted instruction
        Cycles += (INT_Main(1)-1);
    
        //ProfilerData->CurrentTU is a convenient global variable.
        //It is only modified within profiling functions.
        //TODO: It's necessary to detect auto-edges.
        if(PreviousTU){
            if(!TU_IsSameTU(PreviousTU, ProfilerData->CurrentTU)){
                #ifdef TRANSITION_DEBUG
                printf("Transition Detected from 0x%04X to 0x%04X\n", TU_GetStartingAddress(PreviousTU), TU_GetStartingAddress(ProfilerData->CurrentTU));
                #endif

                ExecutionStatistics->DBT_TransitionCount++;
                
                if(PreviousTU->Type != ProfilerData->CurrentTU->Type)
                    ExecutionStatistics->CurrentEpochInfo->HybridTransitions++;
                else if(PreviousTU->Type == TUT_BB)
                    ExecutionStatistics->CurrentEpochInfo->BBTransitions++;
                else
                    ExecutionStatistics->CurrentEpochInfo->CFGTransitions++;
                
                CFG_IncrementEdge(ProfilerData->CurrentCFG, PreviousTU, ProfilerData->CurrentTU);
                
                ProfilerData->EpochCounter--;
            }
        }
        
        //Do Epoch Control        
        if(!ProfilerData->EpochCounter){
            //Reached end of epoch
            printf("End of epoch %ld\n", ProfilerData->Epoch);
            
            //CFG_PrintCFG(ProfilerData->CurrentCFG, "cfg_teste.dot");
            
            __LinkedList *SCCs, *Cycle, *CycleList;
            CFGNode *Node, *HotNode, *CycleNode;
            
            CFG_UpdateNodeSizes(ProfilerData->CurrentCFG);
            
            PROF_PrintCFGs("cfg_teste");            
            
            //TODO: Group TUs together. Use Tarjan's Algorithm.
            SCCs = TARJAN_Run(ProfilerData->CurrentCFG);            
            
            CycleList = LST_New(NULL);
            
            LST_Rewind(SCCs);
            printf("SCCs:\n");
            while((Node = LST_Next(SCCs))){
                printf("0x%04X (HTC: %ld)\n", TU_GetStartingAddress(Node->Unit), Node->SCC_HighestTransitionCount);
                //TODO Call Cycle Detection sorting by HTC.
                LST_Rewind(Node->SCC_HotNodes);
                while((HotNode = LST_Next(Node->SCC_HotNodes))){
                    printf("\t0x%04X (HTC: %ld)\n", TU_GetStartingAddress(HotNode->Unit), HotNode->TARJ_HighestTransitionCount);
                    //Find cycles that include high-transition counts.
                    Cycle = CYCLE_GetCycle(HotNode);
                    if(Cycle){
                        //Output Cycle
                        printf("\tCycle detected!\n\t\t");
                        LST_Rewind(Cycle);
                        while((CycleNode = LST_Next(Cycle))){
                            printf("0x%04X, ", TU_GetStartingAddress(CycleNode->Unit));
                        }
                        printf("\n");          
                        LST_Insert(CycleList, Cycle);
                    }
                }
                printf("\n");
                
            }
            printf("\n");
            
            //Cleanup SCC List
            LST_Destroy(SCCs);
            
            //Analisar Grafos, Fundir e Traduzir!
            LST_Rewind(CycleList);
            
            while((Cycle = LST_Pop(CycleList))){
                
                Temperature = CYCLE_GetTemperature(Cycle);
                
                printf("Temperature for Cycle: %ld\n", Temperature);
                
                ExecutionStatistics->DBT_CompilerInvocations++;
                
                ExecutionStatistics->CurrentEpochInfo->UTFuses++;
                
                CFG_Fuse(ProfilerData->CurrentCFG, Cycle);
                LST_Destroy(Cycle);
            }
            
            LST_Destroy(CycleList);
            
            ProfilerData->CurrentTU = NULL;
            
            CFG_UpdateNodeSizes(ProfilerData->CurrentCFG);
            
            PROF_PrintCFGs("cfg_teste_fused");
            
            NewEpoch();
        }
        
    }
}
